/*
 * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.dtv.lwuit;

import com.sun.dtv.lwuit.animations.Animation;
import com.sun.dtv.lwuit.animations.CommonTransitions;
import com.sun.dtv.lwuit.animations.Transition;
import com.sun.dtv.lwuit.geom.Dimension;
import java.util.Enumeration;
import java.util.Vector;

/**
 * Central class for the API that manages rendering/events and is used to place top
 * level components ({@link Form}) on the "display". Before any Form is shown the Developer must
 * invoke Display.init(Object m) in order to register the current MIDlet.
 * <p>This class handles the main thread for the toolkit referenced here on as the EDT 
 * (Event Dispatch Thread) similar to the Swing EDT. This thread encapsulates the platform
 * specific event delivery and painting semantics and enables threading features such as
 * animations etc...
 * <p>The EDT should not be blocked since paint operations and events would also be blocked 
 * in much the same way as they would be in other platforms. In order to serialize calls back
 * into the EDT use the methods {@link Display#callSerially} &amp; {@link Display#callSeriallyAndWait}.
 * <p>Notice that all LWUIT calls occur on the EDT (events, painting, animations etc...), LWUIT 
 * should normally be manipulated on the EDT as well (hence the {@link Display#callSerially} &amp; 
 * {@link Display#callSeriallyAndWait} methods). Theoretically it should be possible to manipulate
 * some LWUIT features from other threads but this can't be guaranteed to work for all use cases.
 * 
 * @author Chen Fishbein, Shai Almog
 */
public final class Display {
    /**
     * Indicates whether this is a touch device
     */
    private boolean touchScreen;
    
    /**
     * Light mode allows the UI to adapt and show less visual effects/lighter versions
     * of these visual effects to work properly on low end devices.
     */
    private boolean lightMode;
    
    /**
     * Game action for fire
     */
    public static final int GAME_FIRE = 8;

    /**
     * Game action for left key
     */
    public static final int GAME_LEFT = 2;

    /**
     * Game action for right key
     */
    public static final int GAME_RIGHT = 5;

    /**
     * Game action for UP key
     */
    public static final int GAME_UP = 1;

    /**
     * Game action for left key
     */
    public static final int GAME_DOWN = 6;
    
    /**
     * An attribute that encapsulates '#' int value.
     */
    public static final int KEY_POUND = '#';

    private static final Display INSTANCE = new Display();
    
    /**
     * On some devices getKeyCode returns numeric values for game actions,
     * this breaks the code since we filter these values. We pick unused 
     * negative values for game keys and assign them to game keys for getKeyCode.
     */
    private static int[] portableKeyCodes;
    private static int[] portableKeyCodeValues;
    
    private Form current;
    
    private VirtualImplementation implementation = new VirtualImplementation();
    
    /**
     * Contains the call serially pending elements
     */
    private Vector pendingSerialCalls = new Vector();
    
    /**
     * This is the instance of the EDT used internally to indicate whether
     * we are executing on the EDT or some arbitrary thread
     */ 
    private Thread edt;

    /**
     * Contains animations that must be played in full by the EDT before anything further
     * may be processed. This is useful for transitions/intro's etc... that animate without
     * user interaction.
     */
    private Vector animationQueue;

    /**
     * Indicates whether the 3rd softbutton should be supported on this device
     */
    private boolean thirdSoftButton = false;
    
    private boolean editingText;

    /**
     * Ignore all calls to show occuring during edit, they are discarded immediately
     */
    public static final int SHOW_DURING_EDIT_IGNORE = 1;

    /**
     * If show is called while editing text in the native text box an exception is thrown
     */
    public static final int SHOW_DURING_EDIT_EXCEPTION = 2;
    
    /**
     * Allow show to occur during edit and discard all user input at this moment
     */
    public static final int SHOW_DURING_EDIT_ALLOW_DISCARD = 3;

    /**
     * Allow show to occur during edit and save all user input at this moment
     */
    public static final int SHOW_DURING_EDIT_ALLOW_SAVE = 4;

    /**
     * Show will update the current form to which the OK button of the text box
     * will return
     */
    public static final int SHOW_DURING_EDIT_SET_AS_NEXT = 5;
     
    private int showDuringEdit;
    
    static final Object lock = new Object();
    
    
    /** 
     * Private constructor to prevent instanciation
     */
    private Display() {
    }

    Vector getAnimationQueue() {
        return animationQueue;
    }
    
    /**
     * This is the Display initalization method.
     * This method must be called before any Form is shown
     * 
     * @param m the main running MIDlet
     */
    public static void init(Object m) {
        INSTANCE.implementation.init(m);
        
        int width = INSTANCE.getDisplayWidth();
        int height = INSTANCE.getDisplayHeight();
        int colors = INSTANCE.numColors();
        
        // if the resolution is very high and the amount of memory is very low while the device 
        // itself has many colors (requiring 32 bits per pixel) then we should concerve memory
        // by activating light mode.
        INSTANCE.lightMode = colors > 65536 && width * height * 30 > Runtime.getRuntime().totalMemory();
        
        // this can happen on some cases where an application was restarted etc...
        // generally its probably a bug but we can let it slide...
        if(INSTANCE.edt == null) {
            INSTANCE.touchScreen = INSTANCE.implementation.hasPointerEvents();
            // initialize the JWT EDT which from now on will take all responsibility
            // for the event delivery.
            INSTANCE.edt = new Thread(INSTANCE.implementation, "EDT");
            INSTANCE.edt.start();
        }
    }
    
    /**
     * Return the Display instance
     * 
     * @return the Display instance
     */
    public static Display getInstance(){
        return INSTANCE;
    }

    /**
     * Indicates the maximum frames the API will try to draw every second
     * by defult this is set to 10. The advantage of limiting
     * framerate is to allow the CPU to perform other tasks besides drawing.
     * Notice that when no change is occuring on the screen no frame is drawn and
     * so a high/low FPS will have no effect then.
     * 10FPS would be very reasonable for a business application.
     * 
     * @param rate the frame rate
     */
    public void setFramerate(int rate) {
        implementation.setFramerate(rate);
    }
    
    /**
     * Vibrates the device for the given length of time
     * 
     * @param duration length of time to vibrate
     */
    public void vibrate(int duration) {
        implementation.vibrate(duration);
    }
    
    /**
     * Flash the backlight of the device for the given length of time
     * 
     * @param duration length of time to flash the backlight
     */
    public void flashBacklight(int duration) {
        implementation.flashBacklight(duration);
    }

    void blockEvents(boolean block){
        implementation.blockEvents(block);
    }
    /**
     * Invoking the show() method of a form/dialog while the user is editing
     * text in the native text box can have several behaviors: SHOW_DURING_EDIT_IGNORE, 
     * SHOW_DURING_EDIT_EXCEPTION, SHOW_DURING_EDIT_ALLOW_DISCARD, 
     * SHOW_DURING_EDIT_ALLOW_SAVE, SHOW_DURING_EDIT_SET_AS_NEXT
     * 
     * @param showDuringEdit one of the following: SHOW_DURING_EDIT_IGNORE, 
     * SHOW_DURING_EDIT_EXCEPTION, SHOW_DURING_EDIT_ALLOW_DISCARD, 
     * SHOW_DURING_EDIT_ALLOW_SAVE, SHOW_DURING_EDIT_SET_AS_NEXT
     */
    public void setShowDuringEditBehavior(int showDuringEdit) {
        this.showDuringEdit = showDuringEdit;
    }

    /**
     * Returns the status of the show during edit flag
     * 
     * @return one of the following: SHOW_DURING_EDIT_IGNORE, 
     * SHOW_DURING_EDIT_EXCEPTION, SHOW_DURING_EDIT_ALLOW_DISCARD, 
     * SHOW_DURING_EDIT_ALLOW_SAVE, SHOW_DURING_EDIT_SET_AS_NEXT
     */
    public int getShowDuringEditBehavior() {
        return showDuringEdit;
    }

    /**
     * Indicates the maximum frames the API will try to draw every second
     * 
     * @return the frame rate
     */
    public int getFrameRate() {
        return implementation.getFrameRate();
    }
        
    /**
     * Returns true if we are currently in the event dispatch thread.
     * This is useful for generic code that can be used both with the
     * EDT and outside of it.
     * 
     * @return true if we are currently in the event dispatch thread; 
     * otherwise false
     */
    public boolean isEdt() {
        return edt == Thread.currentThread();
    }
    
    /**
     * Plays sound for the dialog
     */
    void playDialogSound(final int type) {
        implementation.playDialogSound(type);
    }
    
    /**
     * Causes the runnable to be invoked on the event dispatch thread. This method
     * returns immediately and will not wait for the serial call to occur 
     * 
     * @param r runnable (NOT A THREAD!) that will be invoked on the EDT serial to
     * the paint and key handling events 
     * @throws IllegalStateException if this method is invoked on the event dispatch thread (e.g. during
     * paint or event handling).
     */
    public void callSerially(Runnable r){
        if(isEdt()) {
            throw new IllegalStateException("Call serially must never be invoked from the EDT");
        }
        synchronized(lock) {
            pendingSerialCalls.addElement(r);
            lock.notify();
        }
    }
    
    
    /**
     * Identical to callSerially with the added benefit of waiting for the Runnable method to complete.
     * 
     * @param r runnable (NOT A THREAD!) that will be invoked on the EDT serial to
     * the paint and key handling events 
     * @throws IllegalStateException if this method is invoked on the event dispatch thread (e.g. during
     * paint or event handling).
     */
    public void callSeriallyAndWait(Runnable r){
        RunnableWrapper c = new RunnableWrapper(r, 0);
        callSerially(c);
        synchronized(lock) {
            while(!c.isDone()) {
                try {
                    lock.wait();
                } catch(InterruptedException err) {}
            }
        }
    }
    
    /**
     * Allows us to "flush" the edt to allow any pending transitions and input to go
     * by before continuing with our other tasks.
     */
    void flushEdt() {
        while(!implementation.shouldEDTSleepNoFormAnimation()) {
            implementation.edtLoopImpl();
        }
    }
    
    boolean hasNoSerialCallsPending() {
        return pendingSerialCalls.size() == 0;
    }
    
    /**
     * Used by the EDT to process all the calls submitted via call serially
     */
    void processSerialCalls() {
        int size = pendingSerialCalls.size();
        if(size > 0) {
            Runnable[] array = new Runnable[size];

            // copy all elements to an array and remove them otherwise invokeAndBlock from
            // within a callSerially() can cause an infinite loop...
            for(int iter = 0 ; iter < size ; iter++) {
                array[iter] = (Runnable)pendingSerialCalls.elementAt(iter);
            }

            pendingSerialCalls.removeAllElements();

            for(int iter = 0 ; iter < size ; iter++) {
                array[iter].run();
            }

            // after finishing an event cycle there might be serial calls waiting
            // to return.
            synchronized(lock){
                lock.notify();
            }
        }
    }

    /**
     * Invokes runnable and blocks the current thread, if the current thread is the
     * edt it will still be blocked however a separate thread would be launched
     * to perform the duties of the EDT while it is blocked. Once blocking is finished
     * the EDT would be restored to its original position. This is very similar to the
     * "foxtrot" Swing toolkit and allows coding "simpler" logic that requires blocking
     * code in the middle of event sensitive areas.
     * 
     * @param r runnable (NOT A THREAD!) that will be invoked synchroniously by this method
     */
    public void invokeAndBlock(Runnable r){
        if(isEdt()) {
            synchronized(lock) {
                // this class allows a runtime exception to propogate correctly out of the
                // internal thread
                RunnableWrapper w = new RunnableWrapper(r, 1);
                Thread t = new Thread(w);
                t.start();

                // loop over the EDT until the thread completes then return
                while(t.isAlive()) {
                    try {
                        if(implementation.shouldEDTSleep()) {
                            lock.wait(50);
                        } else {
                            implementation.edtLoopImpl();
                        }
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
                // if the thread thew an exception we need to throw it onwards
                if(w.getErr() != null) {
                    throw w.getErr();
                }
            }
        } else {
            r.run();
        }
    }

    /**
     * Indicates if this is a touch screen device that will return pen events,
     * defaults to true if the device has pen events but can be overriden by
     * the developer.
     */
    public boolean isTouchScreenDevice() {
        return touchScreen;
    }
    
    /**
     * Indicates if this is a touch screen device that will return pen events,
     * defaults to true if the device has pen events but can be overriden by
     * the developer.
     */
    public void setTouchScreenDevice(boolean touchScreen) {
        this.touchScreen = touchScreen;
    }
    
    /**
     * Displays the given Form on the screen.
     * 
     * @param newForm the Form to Display
     */
    void setCurrent(final Form newForm){
        if(edt == null) {
            throw new IllegalStateException("Initialize must be invoked before setCurrent!");
        }
        
        if(editingText) {
            switch(showDuringEdit) {
                case SHOW_DURING_EDIT_ALLOW_DISCARD:
                    break;
                case SHOW_DURING_EDIT_ALLOW_SAVE:
                    implementation.saveTextBox();
                    break;
                case SHOW_DURING_EDIT_EXCEPTION:
                    throw new IllegalStateException("Show during edit");
                case SHOW_DURING_EDIT_IGNORE:
                    return;
                case SHOW_DURING_EDIT_SET_AS_NEXT:
                    current = newForm;
                    return;
            }
        }
        
        if(!isEdt()) {
            callSerially(new RunnableWrapper(newForm, null));
            return;
        }

        //comentar pra ver o que acontece,,,
        if(current != null){
            current.hidePopups();
            if(current.isInitialized()) {
                current.deinitializeImpl();
            }
        }
        if(!newForm.isInitialized()) {
            newForm.initComponentImpl();
        }
        //fim
        
        newForm.setShouldCalcPreferredSize(true);
        newForm.layoutContainer();

        synchronized(lock) {
            boolean transitionExists = false;
            Form current = this.current;
            if(animationQueue != null && animationQueue.size() > 0) {
                Object o = animationQueue.lastElement();
                if(o instanceof Transition) {
                    current = (Form)((Transition)o).getDestination();
                }
            }

            // make sure the fold menu occurs as expected then set the current
            // to the correct parent!
            if(current != null && current instanceof Dialog && ((Dialog)current).isMenu()) {
                Transition t = current.getTransitionOutAnimator();
                if(t != null) {
                    // go back to the parent form first
                    initTransition(t.copy(), current, ((Dialog)current).getPreviousForm());
                }
                current = ((Dialog)current).getPreviousForm();
            }
            
            // prevent the transition from occuring from a form into itself
            if(newForm != current) {
                if((current != null && current.getTransitionOutAnimator() != null) || newForm.getTransitionInAnimator() != null) {
                    if(animationQueue == null) {
                        animationQueue = new Vector();
                    }
                    // prevent form transitions from breaking our dialog based
                    // transitions which are a bit sensitive
                    if(current != null && (!(newForm instanceof Dialog))) {
                        Transition t = current.getTransitionOutAnimator();
                        if(current != null && t != null) {
                            initTransition(t.copy(), current, newForm);
                            transitionExists = true;
                        }
                    }
                    if(current != null && !(current instanceof Dialog)) {
                        Transition t = newForm.getTransitionInAnimator();
                        if(t != null) {
                            initTransition(t.copy(), current, newForm);
                            transitionExists = true;
                        }
                    }
                }
            } 
            lock.notify();
            
            if(!transitionExists) {
                if(animationQueue == null || animationQueue.size() == 0) {
                    setCurrentForm(newForm);
                } else {
                    // we need to add an empty transition to "serialize" this
                    // screen change...
                    Transition t = CommonTransitions.createEmpty();
                    initTransition(t, current, newForm);
                }
            }
        }
    }
    
    /**
     * Initialize the transition and add it to the queue
     */
    private void initTransition(Transition transition, Form source, Form dest) {
        dest.setVisible(true);
        transition.init(source, dest);
        animationQueue.addElement(transition);
        if(animationQueue.size() == 1) {
            transition.initTransition();
        }
    }
    
    void setCurrentForm(Form newForm){
        if(this.current != null){
            this.current.setVisible(false);
        }
        this.current = newForm;
        this.current.setVisible(true);
        
        //implementation.confirmControlsView();
        
        if(isEdt() && (this.current.getWidth() != implementation.getDisplayWidth() ||
           this.current.getHeight() != implementation.getDisplayHeight())){
           this.current.setSize(new Dimension(implementation.getDisplayWidth(),implementation.getDisplayHeight()));
           this.current.setShouldCalcPreferredSize(true);
           this.current.layoutContainer();
        }
        repaint(current);
    }
    
    /**
     * Indicate to the implementation whether the flush graphics bug exists on this
     * device. By default the flushGraphics bug is set to "true" and only disabled
     * on handsets known 100% to be safe
     * 
     * @param flushGraphicsBug true if the bug exists on this device (the safe choice)
     * false for slightly higher performance.
     */
    public void setFlashGraphicsBug(boolean flushGraphicsBug) {
        implementation.setFlashGraphicsBug(flushGraphicsBug);
    }
    
    /**
     * Indicates whether a delay should exist between calls to flush graphics during
     * transition. In some devices flushGraphics is asynchronious causing it to be
     * very slow with our background thread. The solution is to add a short wait allowing
     * the implementation time to paint the screen. This value is set automatically by default
     * but can be overriden for some devices.
     * 
     * @param transitionDelay -1 for no delay otherwise delay in milliseconds
     */
    public void setTransitionYield(int transitionDelay) {
        implementation.setTransitionYield(transitionDelay);
    }

    /**
     * Encapsulates the editing code which is specific to the platform, some platforms
     * would allow "in place editing" MIDP does not.
     * 
     * @param cmp the {@link TextArea} component
     */
    void editString(Component cmp, int maxSize, int constraint, String text) {
        editingText = true;
        implementation.editString(cmp, maxSize, constraint, text);
        editingText = false;
    }

   /**
    * Returns the video control for the media player
    * 
    * @param player the media player
    * @return the video control for the media player
    */
    Object getVideoControl(Object player) {
        return implementation.getVideoControl(player);
    }
    
    Form getCurrentInternal() {
        return current;
    }
        
    /**
     * Same as getCurrent with the added exception of looking into the future
     * transitions and returning the last current in the transition (the upcoming
     * value for current)
     * 
     * @return the form currently displayed on the screen or null if no form is
     * currently displayed
     */
    Form getCurrentUpcoming() {
        Form upcoming = null;
        
        // we are in the middle of a transition so we should extract the next form
        if(animationQueue != null) {
            Enumeration e = animationQueue.elements();
            while(e.hasMoreElements()) {
                Object o = e.nextElement();
                if(o instanceof Transition) {
                    upcoming = (Form)((Transition)o).getDestination();
                }
            }
        }
        if(upcoming == null) {
            return getCurrent();
        }
        return upcoming;
    }

    /**
     * Return the form currently displayed on the screen or null if no form is
     * currently displayed.
     * 
     * @return the form currently displayed on the screen or null if no form is
     * currently displayed
     */
    public Form getCurrent(){
        if(current != null && current instanceof Dialog && ((Dialog)current).isMenu()) {
            Form p = current.getPreviousForm();
            if(p != null) {
                return p;
            }
            
            // we are in the middle of a transition so we should extract the next form
            Enumeration e = animationQueue.elements();
            while(e.hasMoreElements()) {
                Object o = e.nextElement();
                if(o instanceof Transition) {
                    return (Form)((Transition)o).getDestination();
                }
            }
        }
        return current;
    }
    
    /**
     * Return the number of alpha levels supported by the implementation.
     * 
     * @return the number of alpha levels supported by the implementation
     */
    public int numAlphaLevels(){
        return implementation.numAlphaLevels();
    }

    /**
     * Returns the number of colors applicable on the device, note that the API
     * does not support gray scale devices.
     * 
     * @return the number of colors applicable on the device
     */
    public int numColors() {
        return implementation.numColors();
    }

    /**
     * Light mode allows the UI to adapt and show less visual effects/lighter versions
     * of these visual effects to work properly on low end devices.
     */
    public boolean isLightMode() {
        return lightMode;
    }

    /**
     * Light mode allows the UI to adapt and show less visual effects/lighter versions
     * of these visual effects to work properly on low end devices.
     */
    public void setLightMode(boolean lightMode) {
        this.lightMode = lightMode;
    }
    
    
    /**
     * Return the width of the display
     * 
     * @return the width of the display
     */
    public int getDisplayWidth(){
        return implementation.getDisplayWidth();
    }
    
    /**
     * Return the height of the display
     * 
     * @return the height of the display
     */
    public int getDisplayHeight(){
        return implementation.getDisplayHeight();
    }
    
    /**
     * Causes the given component to repaint, used internally by Form
     * 
     * @param cmp the given component to repaint
     */
    void repaint(final Animation cmp){
        implementation.repaint(cmp);
    }
        
    /**
     * Returns the game action code matching the given key combination
     * 
     * @param keyCode key code received from the event
     * @return game action matching this keycode
     */
    public int getGameAction(int keyCode){
        try {
            // prevent game actions from being returned by numeric keypad thus screwing up
            // keypad based navigation and text input
            if(keyCode >= '0' && keyCode <= '9') {
                return 0;
            }
            if(portableKeyCodes != null) {
                for(int iter = 0 ; iter < portableKeyCodeValues.length ; iter++) {
                    if(portableKeyCodeValues[iter] == keyCode) {
                        return portableKeyCodes[iter];
                    }
                }
            }
            
            return implementation.getGameAction(keyCode);
        } catch(IllegalArgumentException err) {
            // this is a stupid MIDP requirement some implementations throw this
            // exception for some keys
            return 0;
        }
    }
    
    /**
     * Returns the keycode matching the given game action constant (the opposite of getGameAction).
     * On some devices getKeyCode returns numeric keypad values for game actions,
     * this breaks the code since we filter these values (to prevent navigation on '2'). 
     * We pick unused negative values for game keys and assign them to game keys for 
     * getKeyCode so they will work with getGameAction.
     * 
     * @param gameAction game action constant from this class
     * @return keycode matching this constant
     * @deprecated this method doesn't work properly across device and is mocked up here
     * mostly for the case of unit testing. Do not use it for anything other than that! Do
     * not rely on getKeyCode(GAME_*) == keyCodeFromKeyEvent, this will never actually happen!
     */
    public int getKeyCode(int gameAction){
        if(portableKeyCodes == null) {
            portableKeyCodes = new int[] {GAME_DOWN, GAME_LEFT, GAME_RIGHT, GAME_UP, GAME_FIRE};
            portableKeyCodeValues = new int[5];
            int currentValue = -500;
            int offset = 0;
            while(offset < portableKeyCodeValues.length) {
                currentValue--;
                try {
                    if(implementation.getGameAction(currentValue) != 0) {
                        continue;
                    }
                } catch(IllegalArgumentException ignor) {
                    // this is good, the game key is unassigned
                }
                portableKeyCodeValues[offset] = currentValue;
                offset++;
            }
        }
        for(int iter = 0 ; iter < portableKeyCodes.length ; iter++) {
            if(portableKeyCodes[iter] == gameAction) {
                return portableKeyCodeValues[iter];
            }
        }
        return 0;
    }
     
    /**
     * Allows overriding the softkeys initialized by the software to a different value.
     * This method MUST be invoked after init() has completed. 
     * <p>In order to maintain the default value 0 can be passed as a value for a softkey
     * thus resulting in no effect e.g. setSoftkeyCodes(0, 0, 0, -8); will only affect the back key.
     * @param left the left softkey code
     * @param right the right softkey code
     * @param clear the clear softkey code
     * @param back the back softkey code
     */
    public void setSoftkeyCodes(int left, int right, int clear, int back) {
        if(left != 0) {
            Form.leftSK = left;
        }
        if(right != 0) {
            Form.rightSK = right;
        }
        if(clear != 0) {
            Form.clearSK = clear;
        }
        if(back != 0) {
            Form.backSK = back;
        }
    }
    
    
    /**
     * Indicates whether the 3rd softbutton should be supported on this device
     */
    public boolean isThirdSoftButton() {
        return thirdSoftButton;
    }

    /**
     * Indicates whether the 3rd softbutton should be supported on this device
     */
    public void setThirdSoftButton(boolean thirdSoftButton) {
        this.thirdSoftButton = thirdSoftButton;
    }

    VirtualImplementation getVirtualImplementation() {
        return implementation;
    }
    
    

}
